/** 
* @file mhttpd.c
* @brief 简单的 web 服务器
* @author zhunian0322@163.com
* @date 17：16 2018/11/22
* @version 1
* @note   编译  gcc   -D_DEFAULT_SOURCE -o mhttpd mhttpd.c  -std=c99
*         谷歌浏览设置页面编码  utf8  需要另外装插件
*/









#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <dirent.h>
#include <strings.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <unistd.h>

#define SERVER_NAME "mhttpd"
#define SERVER_URL "http://www.test.com/"

#define GET_ARRAY_SIZE(array, len)                \
    {                                             \
        len = (sizeof(array) / sizeof(array[0])); \
    }

static void send_headers(int status, char *title, char *extra_header, char *mime_type, off_t length, time_t mod);
static void send_error(int status, char *title, char *extra_header, char *text);
static void strencode(char *to, size_t tosize, const char *from);
static void file_infos(char *dir, char *name);
static void strdecode(char *to, char *from);
static char *get_mime_type(char *name);
static char *get_time_format(char *timebuf, int len, time_t t);
static int hexit(char c);

#define N 4096
int main(int argc, char **argv)
{
    char line[N * 2], method[N * 2], path[N * 2], protocol[N * 2], idx[N * 4], location[N * 4];
    char *file;
    size_t len;
    int ich, i, n;
    struct stat sb;
    FILE *fp;
    struct dirent **dl;

    if (argc != 2)
    {
        send_error(500, "Internal Error", NULL, "Config error - no dir specified.");
    }

    if (chdir(argv[1]) < 0)
    {
        send_error(500, "Internal Error", NULL, "Config error - couldn't chdir().");
    }

    if (fgets(line, sizeof(line), stdin) == NULL)
        send_error(400, "Bad Request", NULL, "No request found.");

    if (sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol) != 3)
        send_error(400, "Bad Request", NULL, "Can't parse request.");

    while (fgets(line, sizeof(line), stdin) != NULL)
    {
        if (strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0)
        {
            break;
        }
    }

    if (strcasecmp(method, "GET") != 0)
    {
        send_error(501, "Not Implemented", NULL, "That method is not implemented.");
    }

    if (path[0] != '/')
    {
        send_error(400, "Bad Request", NULL, "Bad filename.");
    }

    file = &(path[1]);
    strdecode(file, file);

    if (file[0] == '\0')
        file = "./";

    len = strlen(file);
    if (file[0] == '/' || strcmp(file, "..") == 0 || strncmp(file, "../", 3) == 0 || strstr(file, "/../") != NULL || strcmp(&(file[len - 3]), "/..") == 0)
    {
        send_error(400, "Bad Request", (char *)0, "Illegal filename.");
    }


    if (stat(file, &sb) < 0)
    {
        send_error(404, "Not Found", (char *)0, "File not found.");
    }

    if (S_ISDIR(sb.st_mode))
    {
        if (file[len - 1] != '/')
        {
            snprintf(location, sizeof(location), "Location: %s/", path);
            send_error(302, "Found", location, "Directories must end with a slash.");
        }

        snprintf(idx, sizeof(idx), "%sindex.html", file);
        if (stat(idx, &sb) >= 0)
        {
            file = idx;
            goto do_file;
        }

        send_headers(200, "Ok", NULL, "text/html", -1, sb.st_mtime);

        printf("<html><head><title>Index of %s</title></head>"
               "\n<body bgcolor=\"#99cc99\"><h4>Index of %s</h4>\n<pre>\n",
               file, file);

        n = scandir(file, &dl, NULL, alphasort);

        if (n < 0)
        {
            send_error(500, "scandir", location, "Directories error!.");
        }
        else
        {
            for (i = 0; i < n; ++i)
            {
                file_infos(file, dl[i]->d_name);
            }
        }

        printf("</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n", SERVER_URL, SERVER_NAME);
    }
    else
    {
    do_file:
        fp = fopen(file, "r");
        if (fp == (FILE *)0)
        {
            send_error(403, "Forbidden", (char *)0, "File is protected.");
        }

        send_headers(200, "Ok", (char *)0, get_mime_type(file), sb.st_size, sb.st_mtime);

        while ((ich = getc(fp)) != EOF)
        {
            putchar(ich);
        }
    }

    fflush(stdout);
    return 0;
}

void send_headers(int status, char *title, char *extra_header, char *mime_type, off_t length, time_t mod)
{
    char timebuf[1024];

    printf("HTTP/1.1 %d %s\r\n", status, title);
    printf("Server: %s\r\n", SERVER_NAME);
    printf("Date: %s\r\n", get_time_format(timebuf, 1024, 0));
    printf("Content-Type: %s\r\n", mime_type);
    if (0 <= length)
    {
        printf("Content-Length: %llu\r\n", (uint64_t)length);
    }
    if (mod != (time_t)-1)
    {
        printf("Last-Modified: %s\r\n", get_time_format(timebuf, 1024, mod));
    }
    printf("Connection: close\r\n");
    printf("\r\n");
}

void send_error(int status, char *title, char *extra_header, char *text)
{
    send_headers(status, title, extra_header, "text/html; charset=iso-8859-1", -1, -1);

    printf("<html><title>%d %s</title></head><body>%s</body></html>", status, title, text);

    fflush(stdout);
    exit(1);
}

int hexit(char c)
{
    if (c >= '0' && c <= '9')
    {
        return c - '0';
    }
    else if (c >= 'a' && c <= 'f')
    {
        return c - 'a' + 10;
    }
    else if (c >= 'A' && c <= 'F')
    {
        return c - 'A' + 10;
    }

    return -1;
}

void strencode(char *to, size_t tosize, const char *from)
{
    int tolen = 0;
    for (; *from != '\0' && tolen + 4 < tosize; ++from)
    {
        if (isalnum(*from) || strchr("/_.-~", *from) != NULL)
        {
            *to = *from;
            ++to;
            ++tolen;
        }
        else
        {
            sprintf(to, "%%%02x", (int)*from & 0xff);
            to += 3;
            tolen += 3;
        }
    }

    *to = '\0';
}

void strdecode(char *to, char *from)
{
    for (; *from != '\0'; ++to, ++from)
    {
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
        {
            *to = hexit(from[1]) * 16 + hexit(from[2]);
            from += 2;
        }
        else
        {
            *to = *from;
        }
    }

    *to = '\0';
}

#define FORMAT_DATE "%a, %d %b %Y %H-%M-%S GMT"
char *get_time_format(char *timebuf, int len, time_t t)
{
    memset(timebuf, 0, len);

    if (t == (time_t)0)
    {
        time_t current = time(NULL);
        strftime(timebuf, len, FORMAT_DATE, gmtime(&current));
    }
    else if (t > 0)
    {
        strftime(timebuf, len, FORMAT_DATE, gmtime(&t));
    }

    return timebuf;
}

typedef enum
{
    oUnknown,
    oHtml,
    oHtm,
    oJpeg,
    oJpg,
    oGif,
    oPng,
    oCss,
    oAu,
    oWav,
    oAvi,
    oMov,
    oQt,
    oMpeg,
    oMpe,
    oVrml,
    oWrl,
    oMidi,
    oMid,
    oMp3,
    oOgg,
    oPac
} CONTENT_TYPE_CODES;

static const struct
{
    CONTENT_TYPE_CODES code;
    const char *type;
    const char *suffix;
} HTTP_CONTENT_TYPE[] = {
    {oHtml, "text/html; charset=iso-8859-1", ".html"},
    {oHtm, "text/html; charset=iso-8859-1", ".htm"},
    {oJpeg, "image/jpeg", ".jpeg"},
    {oJpg, "image/jpeg", ".jpg"},
    {oGif, "image/gif", ".gif"},
    {oPng, "image/png", ".png"},
    {oCss, "text/css", ".css"},
    {oAu, "audio/basic", ".au"},
    {oWav, "audio/wav", ".wav"},
    {oAvi, "video/x-msvideo", ".avi"},
    {oMov, "video/quicktime", ".mov"},
    {oQt, "video/quicktime", ".qt"},
    {oMpeg, "video/mpeg", ".mpeg"},
    {oMpe, "video/mpeg", ".mpe"},
    {oVrml, "model/vrml", ".vrml"},
    {oWrl, "model/vrml", ".wrl"},
    {oMidi, "audio/midi", ".midi"},
    {oMid, "audio/midi", ".mid"},
    {oMp3, "audio/mpeg", ".mp3"},
    {oOgg, "application/ogg", ".ogg"},
    {oPac, "application/x-ns-proxy-autoconfig", ".pac"}};

char *get_mime_type(char *name)
{
    char *dot = strrchr(name, '.');

    if (dot)
    {
        int len;
        GET_ARRAY_SIZE(HTTP_CONTENT_TYPE, len);

        for (int i = 0; i < len; ++i)
        {
            if (strcasecmp(dot, HTTP_CONTENT_TYPE[i].suffix) == 0)
            {
                return (char *)HTTP_CONTENT_TYPE[i].type;
            }
        }
    }

    return (char *)"text/plain; charset=iso-8859-1";
}

void file_infos(char *dir, char *name)
{
    static char encoded_name[N];
    static char path[N];
    char timestr[16];
    struct stat sb;

    strencode(encoded_name, sizeof(encoded_name), name);
    snprintf(path, sizeof(path), "%s/%s", dir, name);

    if (lstat(path, &sb) < 0)
        printf("<a href=\"%s\">%-32.32s</a>\n", encoded_name, name);
    else
    {
        strftime(timestr, sizeof(timestr), "%d%b%Y %H:%M", localtime(&sb.st_mtime));
        printf("<a href=\"%s\">%-32.32s</a>    %15s %14lld\n", encoded_name, name, timestr, (int64_t)sb.st_size);
    }
}